Skip to content

fix(opencode): close Linux stop orphan windows - #460

Draft
vansin wants to merge 1 commit into
mainfrom
fix/opencode-stop-orphan-window
Draft

fix(opencode): close Linux stop orphan windows#460
vansin wants to merge 1 commit into
mainfrom
fix/opencode-stop-orphan-window

Conversation

@vansin

@vansin vansin commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Outcome

This draft closes the Linux OpenCode stop-orphan window and records the 2.3.0-preview.35 / 2.5.0-preview.27 evidence pair. It does not publish either package or change any dist-tag.

  • launches OpenCode behind a detached Linux session/process-group supervisor with prepare/activate receipts
  • makes wrapper shutdown TERM-only and requires exact PID/start-time plus empty session/group proof before reporting exit
  • retains owner state on timeout, SIGSTOP, supervisor loss, or residual descendants
  • hardens launch-root markers, stale-root cleanup, and shutdown admission so queued turns cannot reopen a runtime during exit
  • makes bound stop/rename/project/batch/delete paths fail closed instead of falling back to alias/PID SIGKILL behavior
  • preserves failure reports from the iterative gate work

Root cause

The old launcher could time out and SIGKILL only the agent-node wrapper while its detached OpenCode ACP process survived. A second race allowed a turn queued before SIGTERM to enter the OpenCode opening path after shutdown had already cleaned the first client, leaving a credential-scoped launch root behind.

Linux validation on commit 1f1f04d9

All seven RFC-029/OpenCode Docker gates passed on this exact source tree:

  • PR1 registration/start smoke — PASS
  • PR2 mocked ACP shim — PASS
  • PR3 preset + safe-refusal exit code — PASS
  • PR4 real opencode-ai@1.18.1 kernel-live — PASS, zero post-stop PIDs
  • test226 release pin matrix — PASS, zero post-stop PIDs
  • test386 agent-node/security/lifecycle gate — PASS
  • test384 local-package E2E — PASS in 3 consecutive runs; L5 transient roots=0 and L7 global orphan audit=0

git diff --check is clean. The staged secret scan found only explicitly synthetic test fixtures; no real credential was added.

DO NOT MERGE / promote remains frozen

This is intentionally a draft. Review found two unresolved blockers:

  1. Non-Linux bound stop: exact wrapper ownership currently depends on Linux /proc. Windows/macOS bound stop therefore fails closed rather than completing successfully. A platform design plus real Windows validation is required.
  2. Concurrent start/rename: rename snapshots wrapper liveness once, while start does not participate in a durable external lifecycle lock. A start racing after the scan can outlive removal of the old binding/config. Add a per-node cross-process lifecycle lock and deterministic race coverage.

Required before merge/promote: resolve both blockers, rerun affected gates on the new head, pass true Windows validation, and review the draft CI (especially the hard rename-ghost-gate).

@vansin

vansin commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

分流结论:部分落地,但我没能确立 CLI wrapper 那一侧,所以不关,交回作者。

已落地的部分(origin/main):

agent-node/src/runtime/opencode-copresence/process-group.ts  signalExactLinuxProcessGroup
agent-node/src/runtime/opencode-copresence/runtime.ts:340
    if (!signalExactLinuxProcessGroup(identity, "SIGTERM")) {
      throw new Error("refusing to stop OpenCode process group after identity …")

节点侧「精确身份 + TERM-only + 身份不匹配就拒绝」这组性质已经有了。

我没能确立的部分:本 PR 的核心文件 agent-network/src/opencode-wrapper-stop.ts
(以及它的测试)不在 main 上。CLI 侧确实有身份流 agent-network/src/copresence-identity.ts,
但我 grep 过,那个文件里没有任何 opencode 字样,它的调用方是 cli.tstests/test466-codex-copresence-p3

所以下面这个问题我回答不了,需要你或 owner 来定:

anet CLI 停 opencode 时,走的是 copresence-identity 那条通用身份路径,
还是另一条没有身份校验的路径?

如果是前者,本 PR 可以关(性质已被通用模块覆盖);如果是后者,这个 PR 修的洞还在,
只是 behind=301,应该在当前 main 上重做而不是 rebase。

我按「不能确立就不关」处理。另外注意 #715(opencode-cli 在本机不可用,PATH 上是 2025 年的 0.0.55)
仍 OPEN —— 在它解决之前,这条线的端到端验证在本机做不了。

@vansin

vansin commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

Current-main semantic triage (read-only; no code or production change):

This draft should remain open. It is not safely superseded by current main.

Evidence:

  • PR source 1f1f04d9 adds the core files agent-network/src/opencode-wrapper-stop.ts and agent-node/src/runtime/opencode-acp/group-supervisor.ts.
  • Neither file exists on current origin/main (fcaca997).
  • Current main still contains OpenCode launch-root handling, but absence of those two core primitives means the draft's Linux process-group supervision / exact wrapper-stop design cannot be claimed as landed.
  • The draft itself records two unresolved blockers: non-Linux bound stop and concurrent start/rename locking. Current-main inspection does not prove those blockers were independently solved.
  • The branch is now CONFLICTING, so it is not mergeable as-is; however, “conflicting/old” is not evidence that its semantics are obsolete.

Disposition: preserve this PR as design/evidence. Any revival should be a fresh current-main implementation that re-establishes the two unresolved platform/race gates and proves whether the Linux supervisor pieces are still needed. Do not hand-resolve this old branch or close it as superseded without that proof.

@vansin

vansin commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

2026-08-13 maintenance triage (read-only): KEEP DRAFT / DO-NOT-MERGE; not superseded by current main.

Evidence against a premature “already covered” close: current main 034f00647d42d38d5086d7fc057eb7824a441791 still has none of the three core files introduced here: agent-network/src/opencode-wrapper-stop.ts, its test, or agent-node/src/runtime/opencode-acp/group-supervisor.ts. Conversely, the PR body itself records two unresolved blockers: non-Linux bound stop and concurrent start/rename lifecycle locking. Those gates remain the controlling disposition; absence of merge conflicts is not evidence that either behavior is solved.

The branch is also based on a long historical chain and carries 34 files / ~3.8k added lines, so any future revival must be rebuilt against current main with exact source/report coordinates and fresh Linux + true Windows evidence. No close, merge, rebase, production, or package action was taken in this triage.

@vansin

vansin commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

CI 红的诊断:5 处违规确实由本 PR 引入(只读核查,未改本 PR 任何内容)

失败 check no bare rm -rf $VAR。我把守卫脚本本身分别跑在 origin/main 和本 PR 分支上:

origin/main
  ✓ [lint-no-bare-rm-rf] clean — scanned 255 shell file(s) in tests agent-network/tests
  退出码 0

本 PR 分支
  tests/test-rfc029-pr3-preset/run.sh:98        rm -rf "$S4_FAKE_BIN"
  tests/test384-opencode-local-package-e2e/run.sh:733  rm -rf -- "$PERSISTENT_DATA_DIR/log"
  tests/test384-opencode-local-package-e2e/run.sh:735  rm -rf -- "$PERSISTENT_DATA_ESCAPE"
  tests/test386-opencode-agent-node-gate/run.sh:153    rm -rf "$root"
  tests/test386-opencode-agent-node-gate/run.sh:280    rm -rf "$STOP_ROOT" "$WEDGED_ROOT" "$WEDGED_CHILD_FILE"
  退出码 1

main 是干净的 → 这 5 处是本 PR 带进来的,不是历史遗留,也不是守卫误报。

守卫给的改法(它自己打出来的)

1. Source the helper at top of the script:
     source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../lib/safe-rm.sh"
2. Replace 'rm -rf $VAR' with 'safe_rm_rf $VAR'

WHY: see tests/lib/safe-rm.sh (2026-06-16 incident — $HOME fell back to real /home/<user>, wiped projects)
:733/:735 那两条虽然带了 --,但 -- 只防「路径像选项」,不防「变量为空/指到 $HOME」——
守卫要防的是后者。

🔴 记一条我自己差点犯的错

我第一次是用自己写的正则扫全仓,量出「main 上有 11 处」,差点据此报「守卫在 main 上就该红、
所以这是历史遗留」。实际是我的口径更宽:守卫的 SCAN_ROOTS=(tests agent-network/tests),
PATTERN='rm -rf [^/]*\$[A-Za-z_{]'(带 / 前缀的路径不算)。
我那 11 处大多在 agent-node/scripts/docs/tests/scripts/ —— 都在扫描根之外

拿仓库不用的口径去量,得到的是零信息结论。 最后是跑守卫脚本本身才拿到可用的答案。

另外

本 PR 目前 mergeable=CONFLICTING,而且改动面 34 个文件(含 cli.ts +523/-50)。
我没有动它 —— 这是一条正在进行中的大改,rebase 与修 rm -rf 应当由它的作者一起做。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant